﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace DroneLib.Communication
{
    public class UdpStream : MavStream
    {
        public override long Length => _RxStream==null?0: _RxStream.Length;

        public int LocalPort { get; set; } = 14550;
        public IPAddress LocalIP { get; set; }=IPAddress.Any;

        public override bool Connected => _RxStreams.Count > 0;

        IPEndPoint _Remote = null;
        public IPEndPoint Remote 
        {
            get => _Remote;
            set
            {
                _Remote = value;
                if (_RxStreams.ContainsKey(_Remote) == false)
                    _RxStreams[_Remote] = new RingMemoryStream();
                
                _RxStream = _RxStreams[_Remote];
            }
        }

        public IPEndPoint[] RemoteEndpoints => _RxStreams.Keys.ToArray();


        RingMemoryStream _RxStream = null;
        Dictionary<IPEndPoint, RingMemoryStream> _RxStreams = new Dictionary<IPEndPoint, RingMemoryStream>();

        UdpClient _Udp;
        IPEndPoint _LocalEndPoint;
        bool _AsClient = false;

        public UdpStream()
        {

        }

        public UdpStream(int localPort)
        {
            LocalPort = localPort;
        }

        public UdpStream(IPAddress ip,int port,bool asClient=false)
        {
            if(asClient)
            {
                Remote = new IPEndPoint(ip, port);
            }
            else
            {
                LocalIP = ip;
                LocalPort = port;
            }
            _AsClient = asClient;
        }

        public override void Open()
        {
            try
            {
                if(_AsClient)
                {
                    _LocalEndPoint = new IPEndPoint(IPAddress.Any, Remote.Port);
                    //_Udp = new UdpClient(Remote.AddressFamily);
                    _Name = $"UDPCL://{Remote.Address}:{Remote.Port}";
                }
                else
                {
                    _LocalEndPoint = new IPEndPoint(LocalIP, LocalPort);
                    _Name = $"UDP://{LocalIP}:{LocalPort}";
                }

                _Udp = new UdpClient(_LocalEndPoint);
                _Connected = true;

                _Udp.EnableBroadcast = true;
                IsOpen = true;
                _Udp.BeginReceive(UdpDataReceived, null);
                StartMessageLoop();
            }
            catch(Exception ex)
            {
                Error = ex.Message;
                ConnectionFailed = true;
            }
        }

        public override void Reconnect()
        {
            Close();
            Open();
        }

        public override void Close()
        {
            if (IsOpen == false)
                return;

            IsOpen = false;
            _Udp.Close();
            _Udp.Dispose();
            _Udp = null;
            _TerminateLoop = true;
        }


        public override void Flush()
        {
            throw new NotSupportedException();
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            if (IsOpen == false)
                return -1;

            if (_RxStream is null)
                return -1;

            return _RxStream.Read(buffer, offset, count);
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            if (IsOpen == false)
                return;

            _Udp.BeginSend(buffer, count, Remote, UdpDatSent, null);
        }

        private void UdpDataReceived(IAsyncResult ia)
        {
            if (_Udp == null)
                return;

            IPEndPoint remote = null;
            byte[] bytes; 
            try
            {
                bytes = _Udp.EndReceive(ia, ref remote);
            }
            catch(Exception ex)
            {
                return;
            }

            if (Remote == null)
                Remote = remote;

            if(_RxStreams.ContainsKey(remote)==false)
            {
                _RxStreams[remote] = new RingMemoryStream();
            }

            _RxStreams[remote].Write(bytes, 0, bytes.Length);

            if (IsOpen == false)
                return;

            _Udp.BeginReceive(UdpDataReceived, null);
        }

        private void UdpDatSent(IAsyncResult ia)
        {
            try
            {
                _Udp.EndSend(ia);
            }
            catch (Exception ex) { }
        }
    }
}
